Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
85.71% covered (warning)
85.71%
6 / 7
CRAP
98.31% covered (success)
98.31%
58 / 59
ComputeCompletenessOnFamilyUpdateSubscriber
0.00% covered (danger)
0.00%
0 / 1
85.71% covered (warning)
85.71%
6 / 7
17
98.31% covered (success)
98.31%
58 / 59
 __construct
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
9 / 9
 getSubscribedEvents
0.00% covered (danger)
0.00%
0 / 1
1.12
50.00% covered (danger)
50.00%
1 / 2
 checkIfUpdateNeedsToRunBackgroundJob
100.00% covered (success)
100.00%
1 / 1
5
100.00% covered (success)
100.00%
12 / 12
 computeCompletenessOfProductsFamily
100.00% covered (success)
100.00%
1 / 1
6
100.00% covered (success)
100.00%
11 / 11
 areAttributeRequirementsListsUpdated
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
8 / 8
 getOldAttributeRequirementKeys
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
8 / 8
 isAttributeListUpdated
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
9 / 9
<?php
declare(strict_types=1);
namespace Akeneo\Pim\Enrichment\Bundle\EventSubscriber;
use Akeneo\Pim\Enrichment\Bundle\Doctrine\ORM\Query\FindAttributesForFamily;
use Akeneo\Pim\Structure\Component\Model\AttributeInterface;
use Akeneo\Pim\Structure\Component\Model\FamilyInterface;
use Akeneo\Pim\Structure\Component\Repository\AttributeRequirementRepositoryInterface;
use Akeneo\Tool\Bundle\BatchBundle\Launcher\JobLauncherInterface;
use Akeneo\Tool\Component\StorageUtils\Repository\IdentifiableObjectRepositoryInterface;
use Akeneo\Tool\Component\StorageUtils\StorageEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\EventDispatcher\GenericEvent;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
/**
 * Runs a job whenever a family is updated.
 *
 * @author    Samir Boulil <samir.boulil@akeneo.com>
 * @copyright 2017 Akeneo SAS (http://www.akeneo.com)
 * @license   http://opensource.org/licenses/osl-3.0.php  Open Software License (OSL 3.0)
 */
class ComputeCompletenessOnFamilyUpdateSubscriber implements EventSubscriberInterface
{
    /** @var TokenStorageInterface */
    private $tokenStorage;
    /** @var JobLauncherInterface */
    private $jobLauncher;
    /** @var IdentifiableObjectRepositoryInterface */
    private $jobInstanceRepository;
    /** @var string */
    private $jobName;
    /** @var AttributeRequirementRepositoryInterface */
    private $attributeRequirementRepository;
    /** @var FindAttributesForFamily */
    private $findAttributesForFamily;
    /** @var bool */
    private $areAttributeRequirementsUpdatedForFamilies;
    /** @var bool */
    private $isAttributeListUpdated;
    /**
     * @param TokenStorageInterface                   $tokenStorage
     * @param JobLauncherInterface                    $jobLauncher
     * @param IdentifiableObjectRepositoryInterface   $jobInstanceRepository
     * @param AttributeRequirementRepositoryInterface $attributeRequirementRepository
     * @param FindAttributesForFamily                 $findAttributesForFamily
     * @param string                                  $jobName
     */
    public function __construct(
        TokenStorageInterface $tokenStorage,
        JobLauncherInterface $jobLauncher,
        IdentifiableObjectRepositoryInterface $jobInstanceRepository,
        AttributeRequirementRepositoryInterface $attributeRequirementRepository,
        string $jobName,
        FindAttributesForFamily $findAttributesForFamily
    ) {
        $this->tokenStorage = $tokenStorage;
        $this->jobLauncher = $jobLauncher;
        $this->jobInstanceRepository = $jobInstanceRepository;
        $this->attributeRequirementRepository = $attributeRequirementRepository;
        $this->findAttributesForFamily = $findAttributesForFamily;
        $this->jobName = $jobName;
        $this->areAttributeRequirementsUpdatedForFamilies = false;
        $this->isAttributeListUpdated = false;
    }
    /**
     * {@inheritdoc}
     */
    public static function getSubscribedEvents(): array
    {
        return [
            StorageEvents::PRE_SAVE  => 'checkIfUpdateNeedsToRunBackgroundJob',
            StorageEvents::POST_SAVE => 'computeCompletenessOfProductsFamily',
        ];
    }
    /**
     * Defines whether the computation of the completenesses for products belonging to this family should be done in the
     * POST_SAVE event.
     *
     * It needs to recompute completenesses or reindex products only if:
     * - Attribute requirements of the family changed
     * - Attributes list of the family changed (to reindex empty values for the operator IS_EMPTY)
     *
     * @param GenericEvent $event
     *
     * @throws \Doctrine\DBAL\DBALException
     */
    public function checkIfUpdateNeedsToRunBackgroundJob(GenericEvent $event): void
    {
        $subject = $event->getSubject();
        if (!$subject instanceof FamilyInterface) {
            return;
        }
        if (!$event->hasArgument('unitary') || false === $event->getArgument('unitary')) {
            return;
        }
        if (null === $subject->getId()) {
            $this->areAttributeRequirementsUpdatedForFamilies = false;
            $this->isAttributeListUpdated = false;
            return;
        }
        $this->areAttributeRequirementsUpdatedForFamilies = $this->areAttributeRequirementsListsUpdated($subject);
        $this->isAttributeListUpdated = $this->isAttributeListUpdated($subject);
    }
    /**
     * @param GenericEvent $event
     *
     * @throws \InvalidArgumentException
     */
    public function computeCompletenessOfProductsFamily(GenericEvent $event): void
    {
        $subject = $event->getSubject();
        if (!$subject instanceof FamilyInterface) {
            return;
        }
        if (!$event->hasArgument('unitary') || false === $event->getArgument('unitary')) {
            return;
        }
        if ($this->areAttributeRequirementsUpdatedForFamilies || $this->isAttributeListUpdated) {
            $token = $this->tokenStorage->getToken();
            $user = $token->getUser();
            $jobInstance = $this->jobInstanceRepository->findOneByIdentifier($this->jobName);
            $this->jobLauncher->launch($jobInstance, $user, ['family_code' => $subject->getCode()]);
        }
    }
    /**
     * @param FamilyInterface $family
     *
     * @return bool
     */
    private function areAttributeRequirementsListsUpdated(FamilyInterface $family): bool
    {
        $oldAttributeRequirementsKeys = $this->getOldAttributeRequirementKeys($family);
        $newAttributeRequirementsKeys = array_keys($family->getAttributeRequirements());
        sort($oldAttributeRequirementsKeys);
        sort($newAttributeRequirementsKeys);
        $diff = array_merge(
            array_diff($oldAttributeRequirementsKeys, $newAttributeRequirementsKeys),
            array_diff($newAttributeRequirementsKeys, $oldAttributeRequirementsKeys)
        );
        return count($diff) > 0;
    }
    /**
     * @param FamilyInterface $family
     *
     * @return array
     */
    private function getOldAttributeRequirementKeys(FamilyInterface $family): array
    {
        $oldAttributeRequirementsKeys = [];
        $oldAttributeRequirements = $this->attributeRequirementRepository->findRequiredAttributesCodesByFamily($family);
        foreach ($oldAttributeRequirements as $oldAttributeRequirement) {
            $oldAttributeRequirementsKeys[] = sprintf(
                '%s_%s',
                $oldAttributeRequirement['attribute'],
                $oldAttributeRequirement['channel']
            );
        }
        return $oldAttributeRequirementsKeys;
    }
    /**
     * @param FamilyInterface $family
     *
     * @throws \Doctrine\DBAL\DBALException
     *
     * @return bool
     */
    private function isAttributeListUpdated(FamilyInterface $family): bool
    {
        $oldAttributeList = $this->findAttributesForFamily->execute($family);
        $newAttributeList = $family->getAttributes()->map(function (AttributeInterface $attribute) {
            return $attribute->getCode();
        })->toArray();
        sort($oldAttributeList);
        sort($newAttributeList);
        $diff = array_merge(
            array_diff($oldAttributeList, $newAttributeList),
            array_diff($newAttributeList, $oldAttributeList)
        );
        return count($diff) > 0;
    }
}